package sync

import (
	"strings"
	"sync"
	"sync/atomic"

	"gddgame.cc/galaxy/asteroid"
	"gddgame.cc/galaxy/asteroid/cluster"
	"gddgame.cc/galaxy/binocle"
	"gddgame.cc/galaxy/common/feature/orbit"
	"gddgame.cc/galaxy/common/feature/orbit/sync_protocol"
	"gddgame.cc/galaxy/satellite/formula/comet"
	"gddgame.cc/galaxy/utils"
	"gddgame.cc/galaxy/utils/def"
	"gddgame.cc/galaxy/utils/serialize"

	"github.com/vmihailenco/msgpack"
)

const (
	UniqueIdAreaEvent = iota
	SettingAreaEvent
)

type areaCoreInstance struct {
	AutoClose bool            `json:"auto_close" msg:"auto_close"`
	MaxNum    uint32          `json:"max_num" msg:"max_num"`
	Blocks    map[string]bool `json:"blocks" msg:"blocks"`
}

// 用于balance的replicate同步
func (i *areaCoreInstance) Marshal() ([]byte, error) {
	return msgpack.Marshal(i)
}
func (i *areaCoreInstance) Unmarshal(b []byte) error {
	return msgpack.Unmarshal(b, &i)
}

type areaBlockInstance struct {
	Block   *blockInstance
	Service *AreaModeService

	Core *cluster.Client
	cluster.Socket
}

func (block *areaBlockInstance) Join(member *memberInstance, node cluster.Node) {

}
func (block *areaBlockInstance) Leave(member *memberInstance, node cluster.Node) {
	if block.Block.Empty() {
		token := node.Token()
		index := strings.Index(token, ":")
		areaClient := block.Service.GetCore(node.Container(), token[:index])
		areaClient.Leave(block, token)

		_ = node.Close()
	}
}
func (block *areaBlockInstance) UniqueId() uint32 {
	q := block.Service.UniquePool.Get().(chan uint32)
	block.Core.Emit().CallbackTo(func(success bool, data interface{}) {
		if success {
			q <- utils.ToUint32(data)
		} else {
			q <- 0
		}
	}).Send(UniqueIdAreaEvent)
	a := <-q
	block.Service.UniquePool.Put(q)
	return a
}

func (block *areaBlockInstance) Data(data ...interface{}) {

}

func (block *areaBlockInstance) Location(openId string, location []byte) {

}

type AreaModeService struct {
	def.Logger
	binocle.Service

	Block      *BlockService
	UniquePool sync.Pool
}

func (service *AreaModeService) Pool() {
	service.UniquePool = sync.Pool{}
	service.UniquePool.New = func() interface{} {
		return make(chan uint32)
	}
}
func (service *AreaModeService) ID() string {
	return "syncArea"
}
func (service *AreaModeService) Master(node cluster.Node) {

}
func (service *AreaModeService) Init(node cluster.Node) {
	token := node.Token()
	node.SetConfig(cluster.ThreadSafe, true)

	index := strings.Index(token, ":")
	if index >= 0 && token[index] == ':' {
		// block节点
		coreClient := service.GetCore(node.Container(), token[:index])
		block := orbit.LocationCM.NodeBlock(token[index+1:])
		instance := &areaBlockInstance{
			Core:    coreClient,
			Block:   NewBlockInstance(token[:index], block, service, true),
			Service: service,
		}
		instance.Socket = cluster.NewSocket(token, instance)

		node.SetInstance(instance)
		service.Block.InitService(node, instance.Block, instance)

		coreClient.Join(instance, token)
	} else {
		// area节点
		instance := &areaCoreInstance{
			Blocks: map[string]bool{},
		}
		node.SetInstance(instance)
		service.InitCore(node, instance)
	}
}

func (service *AreaModeService) InitCore(node cluster.Node, instance *areaCoreInstance) {
	node.On(cluster.JoinEvent, func(message asteroid.Message) {
		bag := serialize.GetBag()
		bag.SetList(message.List())
		token := bag.ReadString(0)
		bag.Release()

		instance.Blocks[token] = true
		message.Response(true)
		node.Debugf("Area Join:%s => %s", node.Token(), token)
	})
	node.On(cluster.LeaveEvent, func(message asteroid.Message) {
		bag := serialize.GetBag()
		bag.SetList(message.List())
		token := bag.ReadString(0)
		bag.Release()

		if _, ok := instance.Blocks[token]; ok {
			delete(instance.Blocks, token)
		}

		message.Response(true)

		if len(instance.Blocks) == 0 && instance.AutoClose {
			node.Debugf("Area Leave->Close:%s", node.Token())
			_ = node.Close()
		}
	})
	node.On(cluster.CloseEvent, func(message asteroid.Message) {
		node.Println("close")
		_ = node.Close()
	})
	node.On(UniqueIdAreaEvent, func(message asteroid.Message) {
		atomic.AddUint32(&instance.MaxNum, 1)
		message.Response(instance.MaxNum)
	})
	node.On(SettingAreaEvent, func(message asteroid.Message) {
		// 创建区块，设定默认属性
		areaCreateRequest := &sync_protocol.AreaCreateRequest{}
		slot := serialize.GetSlot()
		_ = slot.Set(areaCreateRequest)
		message.UnSerialize(slot)

		instance.AutoClose = areaCreateRequest.AutoClose
		slot.Release()
	})
}
func (service *AreaModeService) InitClient(client *cluster.Client) {
	token := client.ID()

	if strings.Contains(token, ":") {
		service.Block.InitClient(client)
		return
	}

	client.Filter(asteroid.DefaultFilter, func(message asteroid.Message, member def.Member) {
		socket := member.(*comet.Socket)
		d, _ := message.Payload()
		socket.Send(comet.DE(utils.ToByte(message.GetEvent())), d)
	})
}

func (service *AreaModeService) CloseInstance(node cluster.Node) {
	token := node.Token()
	index := strings.Index(token, ":")
	if index >= 0 && token[index] == ':' {
		// block节点
		instance := node.Instance().(*areaBlockInstance)
		service.Block.CloseInstance(node, instance.Block)
	} else {
		// 通知所有block关闭
		instance := node.Instance().(*areaCoreInstance)
		for key, s := range instance.Blocks {
			if !s {
				continue
			}

			client := service.GetWithNodeId(node.Container(), key)
			client.Emit().Send(cluster.CloseEvent)
		}
	}
}

func (service *AreaModeService) CloseClient(client *cluster.Client) {

}

func (service *AreaModeService) NeedBlock() bool {
	return true
}

func (service *AreaModeService) GetWithLocation(c *Client, container cluster.Container, locationByte []byte) *cluster.Client {
	block := c.LocationToBlock(locationByte)
	client := service.GetWithBlock(c, container, block)
	orbit.LocationCM.ReleaseBlock(block)
	return client
}

func (service *AreaModeService) GetWithBlock(c *Client, container cluster.Container, block *orbit.BlockLocation) *cluster.Client {
	//fmt.Println(service.ID(), area, key)
	if block == nil {
		return nil
	}
	return container.Balance().GenerateClient(service.ID(), service.GetNodeId(c.Area, block))
}

func (service *AreaModeService) GetNodeId(area string, block *orbit.BlockLocation) string {
	return area + ":" + block.ToNode()
}

func (service *AreaModeService) GetWithNodeId(container cluster.Container, nodeId string) *cluster.Client {
	return container.Balance().GenerateClient(service.ID(), nodeId)
}

func (service *AreaModeService) GetCore(container cluster.Container, area string) *cluster.Client {
	//fmt.Println(service.ID(), area, key)
	return container.Balance().GenerateClient(service.ID(), area)
}

func (service *AreaModeService) UpdateLocation(c *Client, location *orbit.Location) {

}
